﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Library.Models;
using RPi.I2C.Net;
using Library.Utils;
using log4net;

namespace Library.TwiCommunication
{
    public class TwiCommunicator : ITwiCommunicator
    {
        private static readonly ILog Log = LogManager.GetLogger("TwiCommunicator");
        private const string _i2cPath = "/dev/i2c-0";
        private static object theLock = new object();

        public InDevice ReadDevice(InDevice device)
        {
            if(device == null) return null;
            lock(theLock)
            {
                using (var bus = I2CBus.Open(_i2cPath))
                {
                    Log.Info("Reading InDevice from address: " + device.Address);
                    byte[] value = bus.ReadBytes(device.Address, 1);
                    if(value.Length == 1)
                    {
                        foreach(var pin in device.Pins)
                        {
                            pin.Value = value[0].IsBitSet(pin.Pin);
                        }
                    }
                }
            }

            return device;
        }

        public OutDevice ReadDevice(OutDevice device)
        {
            if (device == null) return null;
            lock (theLock)
            {
                using (var bus = I2CBus.Open(_i2cPath))
                {
                    Log.Info("Reading OutDevice from address: " + device.Address);
                    byte[] value = bus.ReadBytes(device.Address, 1);
                    if (value.Length == 1)
                    {
                        foreach (var pin in device.Pins)
                        {
                            pin.Value = value[0].IsBitSet(pin.Pin);
                        }
                    }
                }
            }

            return device;
        }

        public InOutDevice ReadDevice(InOutDevice device)
        {
            if (device == null) return null;
            lock (theLock)
            {
                using (var bus = I2CBus.Open(_i2cPath))
                {
                    Log.Info("Reading InOutDevice from address: " + device.Address);
                    byte[] value = bus.ReadBytes(device.Address, 1);
                    if (value.Length == 1)
                    {
                        foreach (var pin in device.Pins)
                        {
                            pin.Value = value[0].IsBitSet(pin.Pin);
                        }
                    }
                }
            }

            return device;
        }

        public TermDevice ReadDevice(TermDevice device)
        {
            if (device == null) return null;
            lock (theLock)
            {
                using (var bus = I2CBus.Open(_i2cPath))
                {
                    Log.Info("Reading TermDevice from address: " + device.Address);
                    byte[] value = bus.ReadBytes(device.Address, 2);
                    if (value.Length == 2)
                    {
                        device.Temp = calculateTemp(value);
                    }
                }
            }

            return device;
        }

        public TwiDevice ReadDevice(TwiDevice device)
        {
            if (device.GetType() == typeof(InDevice)) return ReadDevice(device as InDevice);
            if (device.GetType() == typeof(OutDevice)) return ReadDevice(device as OutDevice);
            if (device.GetType() == typeof(InOutDevice)) return ReadDevice(device as InOutDevice);
            if (device.GetType() == typeof(TermDevice)) return ReadDevice(device as TermDevice);
            return device;
        }

        public void WriteDevice(OutDevice device)
        {
            if (device == null) return;
            lock (theLock)
            {
                using (var bus = I2CBus.Open(_i2cPath))
                {
                    byte value = 0xFF;
                    foreach (var pin in device.Pins)
                    {
                        Log.Info("ustawiam pin "+ pin.Pin +" na " + pin.Value);
                        value = value.SetBit(pin.Pin, pin.Value);
                    }
                    //byte value = device.Pins.Aggregate<DevicePin, byte>(0xFF, (current, pin) => current.SetBit(pin.Pin, pin.Value));

                    Log.Info("Writing OutDevice: " + value + " to address: " + device.Address);
                    bus.WriteByte(device.Address, value);
                }
            }
        }

        public void WriteDevice(InOutDevice device)
        {
            if (device == null) return;
            lock (theLock)
            {
                using (var bus = I2CBus.Open(_i2cPath))
                {
                    byte value = device.Pins.Aggregate<DevicePin, byte>(0xFF, (current, pin) => current.SetBit(pin.Pin, pin.Value));

                    Log.Info("Writing InOutDevice: " + value + " to address: " + device.Address);
                    bus.WriteByte(device.Address, value);
                }
            }
        }
        
        public void WriteDevice(TwiDevice device)
        {
            if (device.GetType() == typeof(OutDevice))  WriteDevice(device as OutDevice);
            if (device.GetType() == typeof(InOutDevice)) WriteDevice(device as InOutDevice);
        }

        private double calculateTemp(byte[] bytes)
        {
            if(bytes.Length != 2) return 3.14;
            const int mask = 0x3FF;

            int result = ((bytes[0] << 8) | bytes[1]);
            result = result >> 5;
            if(result == (result | 1 << 10) )
            {
                result = ~result;
                result = result & mask;
                result += 1;
                result = -result;
            }

            return result * 0.125d;
        }
    }



    public interface ITwiCommunicator
    {
        TwiDevice ReadDevice(TwiDevice device);

        InDevice ReadDevice(InDevice device);

        OutDevice ReadDevice(OutDevice device);

        InOutDevice ReadDevice(InOutDevice device);

        TermDevice ReadDevice(TermDevice device);

        void WriteDevice(TwiDevice device);

        void WriteDevice(OutDevice device);

        void WriteDevice(InOutDevice device);
    }
}
